package gov.nist.ssd.swqb.core.search;

import gov.nist.ssd.swqb.util.ErrorDescription;

import com.hp.hpl.jena.rdf.model.Model;
import static gov.nist.ssd.swqb.core.search.AbstractSearch.*;

/**
 * 
 * 
 * @see Search
 * 
 * @author Maxime Gabut
 *
 */
public class SearchFactory {

  private static final int DEFAULT_REQUESTS_SIZE = 5;  
	
	static enum NodeType {
		ANON,
		URINODE,
		LITTERAL,
		ANY,
		ERROR_CASE;
	}

	static enum SyntaxType {
		EXACT_MATCH,
		STANDARD_LUCENE,
		EXTENDED_LUCENE;
	}

	static enum Ternary {
		True,
		False,
		Error
	}
	
	private SearchFactory(String searchText) {;}

  
	/**
	 * Create a search. Remember to check Search validity with isValid();
	 * 
	 * @param text
	 * @param model
	 * @return
	 */
  public static Search getSearch(String text, Model model) {
      return getSearch(text, model, DEFAULT_REQUESTS_SIZE);
  }

  /**
   * Create a search. Remember to check Search validity with isValid();
   *       
   * @param searchText
   * @param model
   * @param requestsSize defines how to split SPARQL requests. optional
   * @return
   */
	 static Search getSearch(String searchText, Model model, int requestsSize) {
		 
		StringBuffer sb = new StringBuffer(searchText.trim());
		
		if(sb.length()==0) {
			return new InvalidSearch("", "SearchText is empty");
		}
		
		ErrorDescription ed = new ErrorDescription();
		
		switch(getNodeType(sb, ed)) {

		case LITTERAL :   trimLitteral(sb);

		switch(getSyntaxType(sb)) {
		case EXACT_MATCH     : return new LitteralExactSearch(
		                          exactMatchText(sb.toString()), model, requestsSize);
		
		case STANDARD_LUCENE : return new LitteralLuceneSearch(
		                          luceneMatchText(sb.toString()), model, requestsSize);
		
		case EXTENDED_LUCENE : lucene2regex(sb);
		                       return new LitteralRegexSearch(
		                           regexMatchText(sb.toString()), model, requestsSize);
		                       
		default : throw new RuntimeException("this never append");
		}

		case ANON :			trimAnon(sb);

		switch(getSyntaxType(sb)) {
		case EXACT_MATCH     : return new AnonExactSearch(
		                        exactMatchText(sb.toString()), model, requestsSize);
		
		case STANDARD_LUCENE :
		case EXTENDED_LUCENE : return new InvalidSearch(
																				sb.toString(),
																				ed.append("only exact match supported with Anonym nodes").toString());
		
		/*	
		case EXTENDED_LUCENE : lucene2regex(sb);
		                       return new AnonRegexSearch(
		                         regexMatchText(sb.toString()), model, resSize);
		*/
		                       
		default : throw new RuntimeException("this never append");
		}

		case URINODE :	trimUriNode(sb);

		switch(getSyntaxType(sb)) {
		case EXACT_MATCH     : return new UriNodeExactSearch(
		                         exactMatchText(sb.toString()), model, requestsSize);
		
		case STANDARD_LUCENE :
		case EXTENDED_LUCENE : lucene2regex(sb);
		                        return new UriNodeRegexSearch(
		                          regexMatchText(sb.toString()), model, requestsSize);
		                        
		default : throw new RuntimeException("this never append");
		}

		case ANY :

			switch(getSyntaxType(sb)) {
			case EXACT_MATCH     :{
				ExactMatchText match = exactMatchText(sb.toString());

				return new AnyNodeExactSearch(match, model, requestsSize);
			}
			
			case STANDARD_LUCENE :{
				LuceneMatchText lmt = luceneMatchText(sb.toString());

				lucene2regex(sb);
				RegexMatchText rmt = regexMatchText(sb.toString());
				
				return new AnyNodeLuceneSearch(lmt, rmt, model, requestsSize);
			}

			case EXTENDED_LUCENE :{
				lucene2regex(sb);
				RegexMatchText rmt = regexMatchText(sb.toString());
				
				return new AnyNodeRegexSearch(rmt, model, requestsSize);
			}
			}

		case ERROR_CASE : return new InvalidSearch(sb.toString(), ed.toString());

		default : throw new RuntimeException("this never append");
		}

	}

	protected static NodeType getNodeType(StringBuffer sb, ErrorDescription ed) {

		switch(isNodeTypeLitteral(sb, ed)) {
		case True : return NodeType.LITTERAL;
		case Error : return NodeType.ERROR_CASE;
		}

		switch(isNodeTypeAnon(sb, ed)) {
		case True : return NodeType.ANON;
		case Error : return NodeType.ERROR_CASE;
		}

		switch(isNodeTypeUriNode(sb, ed)) {
		case True : return NodeType.URINODE;
		case Error :return NodeType.ERROR_CASE;
		}

		switch(isNodeTypeAny(sb, ed)) {
		case True : return NodeType.ANY;
		case False:
		case Error: return NodeType.ERROR_CASE;
				
		default : throw new RuntimeException("this never append");
		}
	}

	/**
	 * 
	 * @param sb TRIMMED search text
	 * @return
	 */
	protected static SyntaxType getSyntaxType(StringBuffer sb) {

		if(isSyntaxExtendedLucene(sb)) {
			return SyntaxType.EXTENDED_LUCENE;
		}
		else if(isWildcardSearch(sb)) {
			return SyntaxType.STANDARD_LUCENE;
		}
		else {
			return SyntaxType.EXACT_MATCH;
		}
	}

	protected static Ternary isNodeTypeLitteral(StringBuffer sb, ErrorDescription ed) {
		int lastCharIndex = sb.length()-1;
		char firstChar = sb.charAt(0);
		char lastChar = sb.charAt(lastCharIndex);

		if(firstChar == '\"') {
			if(lastChar == '\"' && !isCharEscaped(sb, lastCharIndex)) {
				return Ternary.True;
			} else {
			  ed.append("missing final '\"'");
				return Ternary.Error;
			}
		} else {
			return Ternary.False;
		}
	}

	protected static Ternary isNodeTypeUriNode(StringBuffer sb, ErrorDescription ed) {
		int lastCharIndex = sb.length()-1;
		char firstChar = sb.charAt(0);
		char lastChar = sb.charAt(lastCharIndex);

		if(firstChar == '<') {
			if(lastChar == '>' && !isCharEscaped(sb, lastCharIndex)) {
				switch(isNodeTypeAnon(sb, ed)) {
				  case True : return Ternary.False;
				  case False : return Ternary.True;
				  case Error : return Ternary.Error;
				default : throw new RuntimeException("Should never append");
				}
			} else {
			  ed.append("missing final '>'");
				return Ternary.Error;
			}
		} else {
			return Ternary.False;
		}
	}

	protected static Ternary isNodeTypeAnon(StringBuffer sb, ErrorDescription ed) {
		int lastCharIndex = sb.length()-1;

		char firstChar = sb.charAt(0);
		char lastChar = sb.charAt(lastCharIndex);

		if(firstChar == '<') {

			int index = 0;
			boolean more = true;

			while(index < lastCharIndex && more) {

				index++;
				char nextChar = sb.charAt(index);

				if(!(   nextChar == ' ' || nextChar == '\n'
						|| nextChar == '\t'|| nextChar == '\r')) {
					more = false;
				}
			}

			if(   (index+1) < lastCharIndex
					&& sb.charAt(index) == '_' && sb.charAt(index+1) == ':') {

				if(lastChar == '>' && !isCharEscaped(sb, lastCharIndex)) {
					return Ternary.True;
				}
				else {
				  ed.append("missing final '>'");
					return Ternary.Error;
				}

			} else {
				return Ternary.False;
			}
		} else {
			return Ternary.False;
		}
	}

	protected static Ternary isNodeTypeAny(StringBuffer sb, ErrorDescription ed) {
		int lastCharIndex = sb.length()-1;
		char lastChar = sb.charAt(lastCharIndex);
		
		if(lastChar=='\"' && !isCharEscaped(sb, lastCharIndex)) {
			ed.append("lastChar is '\"'. If you want to search this char please escape it (\\\") ");
			return Ternary.False;
		}
		
		if(lastChar=='>' && !isCharEscaped(sb, lastCharIndex)) {
			ed.append("lastChar is '>'. If you want to search this char please escape it (\\>) ");
			return Ternary.False;
		}
		
		return Ternary.True;
	}
	
	protected static void trimLitteral(StringBuffer sb) {
		sb.deleteCharAt(0);
		sb.deleteCharAt(sb.length()-1);
	}

	protected static void trimUriNode(StringBuffer sb) {
		trimLitteral(sb);
	}

	protected static void trimAnon(StringBuffer sb) {
		sb.delete(0, sb.indexOf(":")+1);
		sb.deleteCharAt(sb.length()-1);
	}

	/**
	 * Returns true is the StringBuilder contains a request using lucene's wildcard
	 * features. Every char is considered relevant.
	 * @param sb
	 * @return
	 */
	protected static boolean isWildcardSearch(StringBuffer sb) {
		return isWildcardSearch(sb, 0, sb.length()-1);
	}

	/**
	 * Returns true is the StringBuilder contains a request using lucene's wildcard
	 * features. Relevant chars have to be beetween firstCharIndex and lastCharIndex.
	 * @param sb
	 * @return
	 */
	protected static boolean isWildcardSearch(StringBuffer sb, int firstCharIndex, int lastCharIndex) {

		for(int i=firstCharIndex; i<=lastCharIndex; i++) {
			char charAtI = sb.charAt(i);
			if(charAtI=='*' || charAtI == '?') {
				return true;
			}
		}

		return false;
	}

	/**
	 * Returns true if the StringBuilder contains a request that need to be treated
	 * as "Extended Lucene". Every char is considered relevant.
	 * @param sb
	 * @return
	 */
	protected static boolean isSyntaxExtendedLucene(StringBuffer sb) {
		return isSyntaxExtendedLucene(sb, 0, sb.length()-1);
	}

	/**
	 * Returns true if the StringBuilder contains a request that need to be
	 * treated as "Extended Lucene". Relevant chars have to be beetween
	 * firstCharIndex and lastCharIndex.
	 * @param sb
	 * @param firstCharIndex
	 * @return
	 */
	protected static boolean isSyntaxExtendedLucene(StringBuffer sb, int firstCharIndex, int lastCharIndex) {
		char firstChar = sb.charAt(firstCharIndex);

		return firstChar == '*' || firstChar == '?';
	}

	protected static void lucene2regex(StringBuffer sb) {
		lucene2regex(sb, 0, sb.length()-1);
	}

	protected static void lucene2regex(StringBuffer sb, int firstCharIndex, int lastCharIndex) {
		
		sb.insert(firstCharIndex, '^');         lastCharIndex+=1;
		sb.insert(lastCharIndex+1,'$');         lastCharIndex+=1;

		for(int index = firstCharIndex; index <= lastCharIndex; index++) {
			char charAtIndex = sb.charAt(index);

			if(charAtIndex == '?' && !isCharEscaped(sb, index)) {
				sb.replace(index, index+1, ".");
			}
			else if(charAtIndex == '*' && !isCharEscaped(sb, index)) {
				sb.insert(index, '.');      index+=1;
				lastCharIndex+=1;
			}
		}
	}

	protected static boolean isCharEscaped(StringBuffer sb, int targetChar) {

		int backSlashesCount = 0;
		int index = targetChar-1;

		while(index >=0 && sb.charAt(index)=='\\') {
			backSlashesCount+=1;
			index-=1;
		}

		return backSlashesCount%2 == 1;
	}
}
